package game.square;

import game.border.Border;
import game.border.Wall;
import game.border.WallWithDoor;
import game.square.SquareInterface;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;


/**
 * A class of implementing the squareInterface, it implements all kinds of methods
 * 	to work with, temperature, direction, humidity, borders.
 * 
 * @version     1.0
 * @author      Dries Liebens 
 *   			Loic Vercruysse
 */
public class Square implements SquareInterface{

		/**
		 * Initialize this new square with a wall in the north-direction,
		 * a temperature equal to 20 degrees Celcius and a humidity of 0%.
         * 
         * @effect	The new square is initialized with a temperature of 20 degrees Celcius.
         *          and a humidity of 0% and a wall in north-direction.
         *          | this(20,Temperature.CELCIUS,new Border(Direction.NORTH,new Wall()))
		 */
		public Square() {
			this(20,Temperature.CELCIUS,0,new Border(Direction.NORTH,new Wall()));
		}
	
		/**
		 * Initialize this new square with a wall in the north-direction,
		 * given temperature in given temperature scale and given humidity.
		 * 
		 * @effect	The new square is initialized with given temperature in given temperature scale,
		 * 			given humidity and a wall in north-direction.
		 * 			| this(temperature,scale,humidity,new Border(Direction.NORTH,new Wall()))
		 */
		public Square(double temperature, Temperature scale, double humidity) {
			this(temperature,scale,humidity,new Border(Direction.NORTH,new Wall()));
			
		}
		
        /**
         * Initialize this new square with a given border,a temperature equal to 20 degrees Celcius
         * and a humidity of 0%.
         * 
         * @effect	The new square is initialized with a temperature of 20 degrees Celcius.
         *          and a humidity of 0% and given border.
         *          | this(20,Temperature.CELCIUS,0,border)
         */
        public Square(Border border) {
                this(20,Temperature.CELCIUS,0, border);
        }

        /**
         * Initialize this new square with given temperature and given humidity.
         * 
         * @param	temperature
         *          The temperature for this new square in the preferred scale.
         * @param   humidity
         *          The humidity for this new square.
         * @param   scale
         *          The scale in which the temperature is expressed in.
         * @param	The border we want to add to the square.
         * 
         * @Pre     The given temperature must be a valid temperature.
         *          | canHaveAsTemperature(temperature)
         * @Pre     The given humidity must be a valid humidity.
         *          | isValidHumidity(humidity)
         * @post    The temperature of this new square is equal to the given temperature.
         *          | new.getTemperature() == temperature
         * @post    The humidity of this new square is equal to the given humidity.
         *          | new.getHumidity() == humidity
         * @post    The maximum temperature is initializated at 5000 degrees Celcius.
         *          | new.getMaximumTemperature(Temperature.CELCIUS) == 5000
         * @post    The minimum temperature is initializated at minus 200 degreed Celcius.
         *          | new.getMinimumTemperature(Temperature.CELCIUS) == -200
         * @post    The threshold temperature for heat damage is initializated at 35 degrees Celcius.
         *          | new.getThresholdHeatDamageTemperature(Temperature.CELCIUS) == 35
         * @post    The constant for weighted average for temperature is initializated at 0,2 .
         *          | new.getConstantForWeightedAverageOfTemperature() == 0.2
         * @post    The increment size for heat damage is initializated at 15 for
         *          the temperature scale Celcius.
         *          | new.getIncrementSizeHeatDamage(Temperature.CELCIUS) == 15
         * @post    This square is initializated that it isn't made of slippery material.
         *          | new.isMadeOfSlipperyMaterial() == false;
         * @Post	the square is initialized with the given border.
         * 			|	new.getBorder(border.getDirection()) == border.         
         * @throws  IllegalArgumentException
         *          The given temperature is not a valid temperature for this square.
         *          | !canHaveAsTemperature(temperature, scale)
         * @throws  IllegalArgumentException
         *          The temperature scale is not valid.
         *          | !isValidTemperatureScale(scale)
         * @throws	IllegalBorderException
         * 			|	!canAddBorder(border).
         */
        public Square(double temperature, Temperature scale, double humidity, Border border) 
        				throws IllegalArgumentException, IllegalBorderException {
        	this.maximumTemperature = 5000;
            this.minimumTemperature = -200;
            if(!isValidTemperatureScale(scale))
                    throw new IllegalArgumentException("Non-valid scale!");
            if(!canHaveAsTemperature(temperature, scale))
            		throw new IllegalArgumentException("Non-valid temperature");
            if( !canAddBorder(border))
            	throw new IllegalBorderException();
            
            addBorders(border);
            setHumidity(humidity);
            setTemperature(temperature,scale);
            setThresholdHeatDamageTemperature(35, Temperature.CELCIUS);
            setConstantForWeightedAverageOfTemperature(0.2);
            setIncrementSizeHeatDamage(15, Temperature.CELCIUS);
            setMadeOfSlipperyMaterial(false);
        }       
        /**
         * basic inspector returning the state of the square. 
         */
        @Basic @Raw
        public boolean isTerminated(){
                return this.isTerminated;
        }
        
        /**
         * method terminating the square.
         */
        public void terminate(){
        	
        	if(!isTerminated()){
        		
        		Iterator<Border> iterateBorders = this.getBorders().iterator();
        			while (iterateBorders.hasNext()){
        				iterateBorders.next().terminate();
        			}
        			
        		this.isTerminated = true;
        	}
        			
        }
        
        /**
         * boolean containing the state of the square.
         */
        private boolean isTerminated = false;
        
        /**
         * Checks whether the given temperature scale is a valid temperature scale
         * for a square.
         */
        @Raw
        public  /*static*/ boolean isValidTemperatureScale(Temperature scale){
                return ( (scale == Temperature.CELCIUS) || (scale == Temperature.FAHRENHEIT) 
                                                || (scale == Temperature.KELVIN) );
        }
        /**
         * Returns the temperature in the preferred scale of this square.
         */
        @Basic @Raw
        public double getTemperature(Temperature scale) {
                return Temperature.CELCIUS.convertTo(this.temperature, scale);
        }
        
        /**
         * Sets the given temperature as the temperature.
         */
        @Raw
        public void setTemperature(double temperature,Temperature scale) throws IllegalArgumentException{
                if(!canHaveAsTemperature(temperature,scale))
                        throw new IllegalArgumentException("Non-valid temperature!");
                if(!isValidTemperatureScale(scale))
                        throw new IllegalArgumentException("Non-valid scale!");
                this.temperature = scale.convertTo(temperature, Temperature.CELCIUS);
        }       
        
        /**
         * Checks whether this square can have the given temperature as temperature.
         */
        public boolean canHaveAsTemperature(double temperature,Temperature scale){
                return ( (temperature <= getMaximumTemperature(scale)) 
                                        && (temperature >= getMinimumTemperature(scale)) );
        }
        
        /**
         * Variable referencing the temperature in degrees Celcius of this square.
         */
        private double temperature;
        
        /**
         * Returns the maximum temperature in the preferred scale of this square.
         */
        @Basic @Raw
        public double getMaximumTemperature(Temperature scale){
                return Temperature.CELCIUS.convertTo(this.maximumTemperature, scale);
        }
        
        /**
         * Sets the given temperature as maximum temperature.
         */
        @Raw
        public void setMaximumTemperature(double maximumTemperature,Temperature scale) throws IllegalArgumentException{
                if(!canHaveAsMaximumTemperature(maximumTemperature,scale))
                        throw new IllegalArgumentException("Non-valid maximum temperature!");
                if(!isValidTemperatureScale(scale))
                        throw new IllegalArgumentException("Non-valid scale!");
                this.maximumTemperature = scale.convertTo(maximumTemperature, Temperature.CELCIUS);
        }
        
        /**
         * Checks whether this square can have the given temperature as maximum temperature.
         */
        public boolean canHaveAsMaximumTemperature(double maximumTemperature,Temperature scale){
                return ( (maximumTemperature >= getMinimumTemperature(scale)) 
                                        && (maximumTemperature >= getTemperature(scale)) );
        }
        
        /**
         * Variable referencing the maximum temperature in degrees Celcius of this square.
         */
        private double maximumTemperature;
        
        /**
         * Returns the minimum temperature in the preferred scale of this square.
         */
        @Basic @Raw
        public double getMinimumTemperature(Temperature scale){
                return Temperature.CELCIUS.convertTo(this.minimumTemperature,scale);
        }

        /**
         * Sets the given temperature as minimum temperature.
         */
        @Raw
        public void setMinimumTemperature(double minimumTemperature,Temperature scale) throws IllegalArgumentException{
                if(!canHaveAsMinimumTemperature(minimumTemperature,scale))
                        throw new IllegalArgumentException("Non-valid minimum temperature!");
                if(!isValidTemperatureScale(scale))
                        throw new IllegalArgumentException("Non-valid scale!");
                this.minimumTemperature = scale.convertTo(minimumTemperature,Temperature.CELCIUS);
        }
        
        /**
         * Checks whether this square can have the given temperature as minimum temperature.
         */
        public boolean canHaveAsMinimumTemperature(double minimumTemperature,Temperature scale){
                return ( (minimumTemperature <= getMaximumTemperature(scale)) 
                                        && (minimumTemperature <= getTemperature(scale)) 
                                        && (minimumTemperature >= Temperature.KELVIN.convertTo(0,scale)) );
        }

        /**
         * Variable referencing the minimum temperature in degrees Celcius of this square.
         */
        private double minimumTemperature;

        /**
         * Returns the humidity in percents of this square.
         */
        @Basic @Raw
        public double getHumidity() {
                return this.humidity;
        }
        
        /**
         * Sets a valid humidity as humidity of this square.
         */
        @Raw
        public void setHumidity(double humidity) {
                assert (isValidHumidity(humidity));
                this.humidity = humidity;
        }
        
        /**
         * Checks whether this square can have the given humidity as humidity.
         */
        @Raw
        public boolean isValidHumidity(double humidity) {
                return ( (humidity >= 0) && (humidity <= 100) && ((humidity*100)%1 == 0) );
        }
        
        /**
         * Calculates the rust damage due the humidity of this square.
         */
        public int calculateRustDamage() {
                Double rustDamage =  (getHumidity() - 30)/7;
                if(rustDamage % 1 != 0)
                        rustDamage = rustDamage%1;
                return rustDamage.intValue();
        }
        
        /**
         * Variable referencing the humidity of this square.
         */
        private double humidity; 
        
        /**
         * Returns the threshold for heat damage in the preferred scale for squares.
         */
        @Basic @Raw
        public double getThresholdHeatDamageTemperature(Temperature scale) throws IllegalArgumentException {
                if(!isValidTemperatureScale(scale))
                        throw new IllegalArgumentException("Non-valid scale!");
                return Temperature.CELCIUS.convertTo(Square.thresholdHeatDamageTemperature,scale);
        }
        
        /**
         * Sets the threshold for heat damage in the preferred scale for squares.
         */
        @Raw
        public void setThresholdHeatDamageTemperature(double thresholdHeatDamageTemperature,Temperature scale) 
                throws IllegalArgumentException {
                if(!isValidThresholdHeatDamageTemperature(thresholdHeatDamageTemperature,scale))
                        throw new IllegalArgumentException("Non-valid temperature!");
                if(!isValidTemperatureScale(scale))
                        throw new IllegalArgumentException("Non-valid temperature scale");
                Square.thresholdHeatDamageTemperature = scale.convertTo(thresholdHeatDamageTemperature, Temperature.CELCIUS);
        }
        
        /**
         * Checks whether this square can have the given thresholdHeatDamageTemperature as thresholdHeatDamageTemperature.
         */
        @Raw
        public boolean isValidThresholdHeatDamageTemperature(double thresholdHeatDamageTemperature,Temperature scale) {
                return thresholdHeatDamageTemperature > getThresholdColdDamageTemperature(scale);
        }
        
        /**
         * Variable referencing the threshold for heat damage in degrees Celcius this square.
         */
        private static double thresholdHeatDamageTemperature;
        
        /**
         * Returns the threshold for cold damage in the preferred scale for squares.
         */
        @Basic @Raw
        public double getThresholdColdDamageTemperature(Temperature scale) {
                return Temperature.CELCIUS.convertTo(thresholdColdDamageTemperature, scale);
        }
        
        /**
         * Variable referencing the threshold for cold damage in degrees Celcius for squares.
         */
        public static final double thresholdColdDamageTemperature = -5;
        
        /**
         * Returns the increment size based on the preferred temperature scale for squares.
         */
        @Basic @Raw
        public  double getIncrementSizeHeatDamage(Temperature scale) {
                return Temperature.CELCIUS.convertTo(0,scale) 
                                + Temperature.CELCIUS.convertTo(incrementSizeHeatDamage,scale);
        }
        
        /**
         * Sets the increment size for heat damage.
         */
        @Raw
        public void setIncrementSizeHeatDamage(double incrementSizeHeatDamage,Temperature scale) 
                throws IllegalArgumentException {
                if(!isValidIncrementSizeHeatDamage(incrementSizeHeatDamage))
                        throw new IllegalArgumentException("Non-valid increment size!");
                if(!isValidTemperatureScale(scale))
                        throw new IllegalArgumentException("Non-valid scale!");
                
                double incrementSizeHeatDamageInCelciusScale = incrementSizeHeatDamage + Temperature.CELCIUS.convertTo(0, scale);
                Square.incrementSizeHeatDamage = scale.convertTo(incrementSizeHeatDamageInCelciusScale, 
                                                                                                                Temperature.CELCIUS);
        }
        
        /**
         * Checks if squares can have the given incrementSizeHeatDamage as incrementSizeHeatDamage.
         */
        @Raw
        public boolean isValidIncrementSizeHeatDamage(double incrementSizeHeatDamage) {
                return incrementSizeHeatDamage > 0;
        }
        
        /**
         * Variable referencing the increment size for heat damage for squares.
         * This increment size is calculated on the scale degrees Celcius. 
         */
        private static double incrementSizeHeatDamage;
        
        /**
         * Calculates the heat damage due the temperature of this square.
         */
        public int calculateHeatDamage() {
                if(getTemperature(Temperature.CELCIUS) <= getThresholdHeatDamageTemperature(Temperature.CELCIUS))
                        return 0;
                Double heatDamage = (getTemperature(Temperature.CELCIUS) 
                                - getThresholdHeatDamageTemperature(Temperature.CELCIUS))/getIncrementSizeHeatDamage(Temperature.CELCIUS);
                if( (heatDamage % 1) != 0)
                        heatDamage = (heatDamage / 1);
                return heatDamage.intValue();
        }
        
        /**
         * Calculates the cold damage due the temperature of this square.
         */
        public int calculateColdDamage() {
                if(getTemperature(Temperature.CELCIUS) >= getThresholdColdDamageTemperature(Temperature.CELCIUS))
                        return 0;
                Double coldDamage =  (Math.abs(getTemperature(Temperature.CELCIUS)
                                        - Math.abs(getThresholdColdDamageTemperature(Temperature.CELCIUS)))/10);
                if( (coldDamage % 1) != 0)
                        coldDamage = coldDamage / 1;
                return coldDamage.intValue();
        }

        /**
         * Sets if this square is made of slippery material.
         */
        @Raw
        public void setMadeOfSlipperyMaterial(boolean madeOfSlipperyMaterial) {
                this.madeOfSlipperyMaterial = madeOfSlipperyMaterial;
        }       
        
        /**
         * Returns if this square is made of slippery material.
         */
        @Basic @Raw
        public boolean isMadeOfSlipperyMaterial() {
                return this.madeOfSlipperyMaterial;
        }

        /**
         * Variable referencing if this square is made of slippery material.
         */
        private boolean madeOfSlipperyMaterial;
        
        /**
         * Calculates if the floor is slippery
         */
        public boolean isSlippery(){ 
                return (isMadeOfSlipperyMaterial() || isSlipperyDueCircumstances());
        }
        
        /**
         * Calculates if the floor is slippery.
         */
        public boolean isSlipperyDueCircumstances() {
        	if((getTemperature(Temperature.CELCIUS) > 0) && (getHumidity() == 100))
                return true;
	        if((getTemperature(Temperature.CELCIUS) <= 0) && (getHumidity() > 10))
	                return true;
	        return false;
        }
        
        /**
         * Calculates the inhabitability of this square.
         */
        public double calculateInhabitability() {
                double rootOfTriplePowerOfHeatDamage = Math.sqrt(calculateHeatDamage() * calculateHeatDamage()
                                * calculateHeatDamage());
                double rootOfHumidityWithConstant = Math.sqrt(101 - getHumidity());
                double rootOfColdDamage = Math.sqrt(calculateColdDamage());
                return -(rootOfTriplePowerOfHeatDamage / rootOfHumidityWithConstant) - rootOfColdDamage;
        }

        
        /**
         * Removes one or more borders of this square.
         */
        @Raw
        public void removeBorders(Direction... directions) { 
                for(Direction direction : directions){
                        if(canBeRemoved(direction)){
                        	getBorder(direction).setSquare(null);
							removeBorder(direction);
                        }
                }
        }
        /**
         * private method, deleting a border in a given direction from the list.
          */
        @Raw
		private void removeBorder(Direction direction) {
        	Border border = getBorder(direction);
        	if(border != null) {
        		this.borders.remove(border);
        	}
		}
        
        /**
         * method which checks whether an existing border in the given direction can be removed 
         */
        @Raw
        public boolean canBeRemoved(Direction direction){
                if( direction == null || !hasBorder(direction) || (borders.size() == 1))
                	return false;
                return true;
        }
        
        /**
         * Returns if this square has a border at the given direction.
         */
        @Raw
        public boolean hasBorder(Direction direction) {
        	checkBorders();
    		if(isValidDirection(direction)){
                Iterator<Border> iterateBorders =  getBorders().iterator();
                while( iterateBorders.hasNext()){
                        if(iterateBorders.next().getDirection() == direction )
                                return true;
                }
            }
            return false;
        }
        
        /**
         * Checks if all borders have an obstacle.
         * If not the border will be removed.
         */
        public void checkBorders() {
        	Iterator<Border> iterateBorders =  getBorders().iterator();
        	while(iterateBorders.hasNext()) {
        		Border border = iterateBorders.next();
        		if(border.getObstacle() == null){
        			borders.remove(border);
        		}
        	}
        }
        
        /**
         * Returns the borders of this square.
         */
        @Basic @Raw
        public List<Border> getBorders() {
                return new ArrayList<Border>(borders);
        }
        
        /**
         * Returns the border in given direction.
         */
        @Raw
        public Border getBorder(Direction direction){
                if(hasBorder(direction)){
                        Iterator<Border> iterateBorders =  getBorders().iterator();
                        Border demandedBorder;
                        while( iterateBorders.hasNext()){
                                demandedBorder = iterateBorders.next();                         
                                if(demandedBorder.getDirection() == direction )
                                        return demandedBorder;
                        }
                }
                return null;
        }
        
        /**
         * this method changes an existing border with an other valid border.
         */
        @Raw
        public void modifyBorder(Border border)  {
        	 if(border != null){
        		 Border oldBorderInDirection = null;
        		 Border tempBorder = null;
        		 if(hasBorder(border.getDirection())){
        			 oldBorderInDirection = this.getBorder(border.getDirection());
        			 tempBorder = this.getBorder(border.getDirection());
        			 tempBorder.setSquare(null);
        			 this.removeBorder(border.getDirection());
        		 }
        		 
        		 if(canAddBorder(border)) {
        			 addBorders(border);
        		 }
        		 else {
        			 addBorders(oldBorderInDirection);
        		 }
        	 }  
        }
        
        /**
         * Add a border to this square.
         */
        @Raw
        public void addBorders(Border... borders) {
                for(Border border : borders){
                        if(canAddBorder(border)){
                                border.setSquare(this);
                                this.borders.add(border);
                        }
                }
        }
        
        /**
         * Method which checks whether it is possible to add a given border in a given direction
         */
        @Raw
        public boolean canAddBorder(Border border){
        		if( border.isTerminated()) 
                    return false;
                if( !canHaveAsBorder(border))
                    return false;
                if( hasBorder(border.getDirection()))
                    return false;  
                if( hasNeighbour(border.getDirection()))
                	return false;
                if( Wall.isWall(border.getObstacle()) )
                	return true;
                if( WallWithDoor.isWallWithDoor(border.getObstacle()) && (border.getDirection() == Direction.FLOOR) )
                	return false;
                
                if( WallWithDoor.isWallWithDoor(border.getObstacle())){
                Iterator<Border> borderIteration =      this.getBorders().iterator();
                int counter = 0;
                        
                while(borderIteration.hasNext()){
                        if (WallWithDoor.isWallWithDoor(borderIteration.next().getObstacle()))
                                counter++;
                }       
                        
                if(counter < 3)
                        return true;}
                return false;
        }
        
        /**
         * verifies the consistency between the square and the border.
         */
        @Raw
        public boolean hasProperBorder(Direction direction){
        	if(isValidDirection(direction)){
                if (hasBorder(direction))
                	return( canHaveAsBorder(getBorder(direction)) 	
                				&& (this.getBorder(direction).getSquare() == this));
                if ( !hasBorder(direction))
                	return (this.getBorder(direction) == null);
        	}
                return false;
        }
 
		/**
         * checks if a square could potentially have the border.
         */
        @Raw
        public boolean canHaveAsBorder(Border border) {
                if ( this.isTerminated())
                        return border == null;
                if(border == null)
                	return false;
                if(border.getObstacle() == null)
                	return false;
                
                return true;
        }

        /**
         * Variable referencing the borders of this square.
         */
        private List<Border> borders = new ArrayList<Border>(); 
        
        
        
        /**
         * Returns the constant for weighted average for temperatures for squares.
         */
        @Basic @Raw
        public double getConstantForWeightedAverageOfTemperature() {
                return Square.constantForWeightedAverageOfTemperature;
        }
        
        /**
         * Sets the constant for weighted average for temperatures.
         */
        @Raw
        public void setConstantForWeightedAverageOfTemperature(double constantForWeightedAverageOfTemperature) 
                throws IllegalArgumentException {
                if(!isValidConstantForWeightedAverageOfTemperature(constantForWeightedAverageOfTemperature))
                        throw new IllegalArgumentException();
                Square.constantForWeightedAverageOfTemperature = constantForWeightedAverageOfTemperature;
        }
        
        /**
         * Checks if squares can have the given constant as constant for weighted average for temperatures.
         */
        @Raw
        public boolean isValidConstantForWeightedAverageOfTemperature(double constantForWeightedAverageOfTemperature) {
                return ( (constantForWeightedAverageOfTemperature >= 0.1) && (constantForWeightedAverageOfTemperature <= 0.4) );
        }
        
        /**
         * Variable referencing the constant for weighted average of temperatures of squares.
         */
        private static double constantForWeightedAverageOfTemperature;
        
        /**
         * Merges the given square with this square in given direction.
         */
        public void mergeWith(SquareInterface square, Direction direction) throws IllegalArgumentException{
                if(square == null)
                        throw new IllegalArgumentException();
                if(direction == null)
                        throw new IllegalArgumentException();
                
                // delete borders
                square.removeBorders(direction);
                this.removeBorders(direction);
                // set humidity
                double averageHumidity = calculateAverage(square.getHumidity(),getHumidity());
                square.setHumidity(averageHumidity);
                this.setHumidity(averageHumidity);
                // set temperature
                double weightedAverageTemperature = calculateWeightedAverage(square.getTemperature(Temperature.CELCIUS),
                                                                                                getTemperature(Temperature.CELCIUS) );
                square.setTemperature(weightedAverageTemperature, Temperature.CELCIUS);
                this.setTemperature(weightedAverageTemperature, Temperature.CELCIUS);
        }
        
        /**
         * Calculates the average of the two given numbers.
         */
        @Raw
        public double calculateAverage(double numberOne,double numberTwo) {
                return (numberOne + numberTwo)/2;
        }
        
        /**
         * Calculates the weighted average of the two given numbers.
         */
        @Raw
        public double calculateWeightedAverage(double numberOne,double numberTwo) {
                double average = calculateAverage(numberOne,numberTwo);
                double weightOne = calculateWeight(numberOne,average);
                double weightTwo = calculateWeight(numberTwo,average);
                return (weightOne*numberOne + weightTwo*numberTwo)/2;
        }
        
        /**
         * Calculates the weight for a weighted average.
         */
        @Raw
        public double calculateWeight(double number,double average) {
                return (1-getConstantForWeightedAverageOfTemperature())*number/average
                + getConstantForWeightedAverageOfTemperature();
        }   
        
        /**
         * removes borders in the given directions
         */
        @Raw
        public void removeNeighbour(Direction... directions){
        	for (Direction direction : directions)
        		if (isValidDirection(direction))
        		this.neighbours.remove(direction);
        }
        
        /**
         * Sets a neigbour in  given directions.
         */
        @Basic @Raw
        public void setNeighbour(Direction direction, SquareInterface square){
    		if(isValidDirection(direction))
    			if(canAddNeighbour(direction))
    				this.addNeighbour(direction,square);
        }
        
        /**
         * private method adding a direction to the neigbours list.
         */
        @Raw
        private void addNeighbour(Direction direction, SquareInterface square){
        	neighbours.put(direction,square);
        }
        
        /**
         * method checking if there is a neighbour in a given direction.
         */
        @Raw
        public boolean hasNeighbour(Direction direction){
        	return neighbours.containsKey(direction);
        }
        
        /**
         * returns the neighbouring square in a given direction of this square.
         */
        @Raw @Basic
        public SquareInterface getNeighbour(Direction direction){
        	if (hasNeighbour(direction))
        		return neighbours.get(direction);
        	return null;
        }
        
        
        /**
         * checks whether or not a neighbour can be added in the direction
         */
        @Raw
        public boolean canAddNeighbour(Direction direction){
        	return !hasNeighbour(direction);
        }
        
        /**
         * list containing the neigbours.
         */
        private Map<Direction, SquareInterface> neighbours = new HashMap<Direction, SquareInterface>();

        /**
         * returns destinations where one could move to in one step.
         */
		@Override
		public List<SquareInterface> getDirectDestinations() {
			List<SquareInterface> destinations = new ArrayList<SquareInterface>(); 
			
			//We assumed, that unopened doors can be opened, and thus squares behind the door can be reached.
			for( Direction direction : neighbours.keySet())
	         	if(!this.hasBorder(direction) || (this.hasBorder(direction) && !Wall.isWall(this.getBorder(direction).getObstacle())))
	          	destinations.add(this.getNeighbour(direction));
			
			for (SquareInterface specialDestination: getSpecialDestinations()){
				destinations.add(specialDestination);}
	          	
			return destinations;
		}
		/**
		 * gives a list of special destinations.
		 */
		public List<SquareInterface> getSpecialDestinations(){
			return new ArrayList<SquareInterface>();
		}

		/**
         * checks if a direction is valid
         */
        @Raw
        public boolean isValidDirection(Direction direction){
        	return(direction!= null && ( direction.equals(Direction.NORTH) 
        			|| direction.equals(Direction.SOUTH)
        			||direction.equals(Direction.CEILING) || direction.equals(Direction.FLOOR)
        			|| direction.equals(Direction.WEST) || direction.equals(Direction.EAST)));
        }
        
        /**
         * Checks if this square can reach the given one, by an interlinking of other squares.
         * 
         * @param 	square
         * 			The square from we which we wish to check if it is interlinkable with this square.
         * @param 	beenThere
         * 			A list of previous squares where we have been already. If the method is invoked by human,
         * 			the list has to be an empty list. If not, an empty list is created.
         * @return	returns true if the 2 squares are interlinked.
         * 			|	if(square == null || beenThere == null)
         * 			|		result == false
         * 			|	if( this.equals(square))
         * 			|		result == true
         *			|	if(this.getDestinations().contains(square))
         *			|		result == true
         *			|	if (beenThere.contains(this))
         *			|		result == false
         *			|
         *			|	boolean reachable = false;
         *			|	Iterator<SquareInterface> iterator = this.getDestinations().iterator();
         *			|
         *			|	while(iterator.hasNext()){
         * 			|		SquareInterface temp = iterator.next();
         * 			|		beenThere.add(this);
         *			|		reachable = reachable || temp.reachable(square, beenThere);
         *			|	}
         *          |	result == reachable;
         */
        public boolean canReach(SquareInterface square, List<SquareInterface> beenThere){
        	if(square == null || beenThere == null)
        		return false;
        	if( this.equals(square))
        		return true;
        	if(this.getDirectDestinations().contains(square))
        		return true;
        	if (beenThere.contains(this))
        		return false;
        	if(beenThere.contains(square))
        		return true;
        	
        	if(counterForCanReachMethod == Long.MIN_VALUE)
        		beenThere = new ArrayList<SquareInterface>();
        		
        	counterForCanReachMethod++;
        	
        	boolean reachable = false;
        	Iterator<SquareInterface> iterator = this.getDirectDestinations().iterator();
        		
        	while(iterator.hasNext()){
        		SquareInterface temp = iterator.next();
        		beenThere.add(this);
        		reachable = reachable || temp.canReach(square, beenThere);
        	}
        	
        	counterForCanReachMethod--;
        	
        	return reachable;
        }
        
        /**
         * Variable referencing a counter for the method canReach.
         */
        private long counterForCanReachMethod = Long.MIN_VALUE;
}
